Executive summary
조사 목적
주차대란이라는 이야기를 심심치 않게 들을 수 있습니다. 저 역시도 자가차량 구입 이후 삶의 편의성이 많이 개선되었지만, 반대로 주차 문제로 스트레스를 겪는 경우도 심심찮게 마주하게 됩니다. 당장 서울대에서도 주차공간 부족으로 인해 남들보다 1~2시간 일찍 등교를 하는 자구책을 마련하는 등 자가차량 이용 시 주차에 대한 고민을 항상 하게 됩니다.
이에 서울시에서 운영하는 공영주차장의 위치, 분포 및 가격 정보를 조사하고, 과연 적절한 서비스가 제공되고 있는지 궁금증을 해결해 보고자 합니다. 필요 시 이종데이터와 결합하여 분석도 진행 할 예정이나, 이에 대한 구체적인 해결책을 논하지는 않을 예정입니다.
서울 시 공영 주차장 운영 현황 분석
library(httr)
library(rvest)
library(dplyr)
— 상기 API를 통해서는 최근 10,000번째 데이터까지만 호출 할 수 있음
— API의 출력값은 1개 노상주차장 내 모든 개별 주차공간의 좌표를 담고 있음
— 서울시 공영주차장 수는 666개로 추정하며, 중복값을 제외하고 총 666개의 obs.를 모을 때까지 반복적으로 업데이트 (1분 주기 업데이트)
parking_info = data.frame()
# 1회차 실행시에는 아래 코드 대신 비어있는 데이터 프레임 생성
original_set = read.csv('d:/github/r/visualization/final_proj/park_info.csv')[,-1]
# original_set = data.frame()
key = '6b46504b456b6f6f313130734d624851'
for (i in 0:9){
# 시작번호는 1번부터 9,001까지 1,000개 단위로 호출
start_num = i*1000+1
url = paste0('http://openapi.seoul.go.kr:8088/',
key,
'/xml/SearchParkingInfo/',
start_num,
'/',
start_num+999)
url_xml = read_xml(GET(url))
# 노드명 row 기준으로 주차장 정보 필터
item_list = xml_nodes(url_xml, 'row')
item_list = lapply(item_list, function(x) return(xml_text(xml_children(x))))
item_dat = do.call('rbind',item_list)
item_dat = data.frame(item_dat, stringsAsFactors = F)
tmp = xml_nodes(url_xml, 'row')
colnames_dat = html_name(xml_children(tmp[[1]]))
# 데이터 프레임화 및 열이름 부여
colnames(item_dat) = colnames_dat
# 미리 만들어 둔 데이터프레임(parking_info)에 크롤링 데이터 추가
parking_info = rbind(parking_info ,item_dat)
# PARKING_CODE 기준으로 중복 자료 삭제
parking_info = parking_info[!duplicated(parking_info$PARKING_CODE),]
# 에러 체크
#print(i)
}
—처음에 불러온 ’park_info.csv’파일은 666개의 주차장 정보 수집 후 숫자가 늘지 않아, 총 공영 주차장 개수는 666개일 것으로 추정
# 불필요한 정보 제거 (개장시간, 주차장 종류 등)
parking_info_summarized = parking_info[c(1:3, 7, 8, 21, 22, 24:27)]
# 기존에 크롤링한 자료와 비교하여 업데이트 진행
parking_info_renewed = rbind(parking_info_summarized, original_set)
parking_info_renewed = parking_info_summarized[!duplicated(parking_info_summarized$PARKING_CODE),]
# 데이터가 추가 되었을 시 파일로 저장
if (dim(original_set)[1] < dim(parking_info_renewed)[1]){
write.csv(parking_info_renewed, 'd:/github/r/visualization/final_proj/park_info.csv')
}
# 데이터 재 호출 (factor > numeric)
PI = read.csv('d:/github/r/visualization/final_proj/park_info.csv')[,-1]
# 주차장 주소를 구 단위로 분류하고 factor로 변환
PI$ADDR = as.factor(gsub('구 [가-힣0-9a-zA-Z ()~-]*', '구', PI$ADDR))
# 1. 시간당 주차료 산정 이전에 비어있는 값 배정
tmp = PI %>% filter(PAY_NM=='유료') %>% filter(ADD_RATES==0)
tmp$ADD_RATES = tmp$RATES
tmp$ADD_TIME_RATE = tmp$TIME_RATE
PI = rbind(tmp,PI)
PI = PI[!duplicated(PI$PARKING_CODE),]
# 2. 1번 조치에도 누락되어있는 데이터 수기로 추가 http://parking.seoul.go.kr/web/MapMain.aspx
PI[PI$PARKING_CODE%in%c(173141, 171802),]$ADD_RATES = PI[PI$PARKING_CODE%in%c(173141, 171802),]$RATES
PI[PI$PARKING_CODE%in%c(173141, 171802),]$ADD_TIME_RATE = PI[PI$PARKING_CODE%in%c(173141, 171802),]$TIME_RATE
PI[PI$PARKING_CODE==1010089,]$TIME_RATE = 0
# 3. 시간당 주차료를 계산하고, 무료주차장인 경우(is.nan)에는 0을 기입
attach(PI)
RATE_HOUR = RATES+ADD_RATES*(60-TIME_RATE)/ADD_TIME_RATE
RATE_HOUR[is.nan(RATE_HOUR)] = 0
detach(PI)
# 4. data.frame에 병합
PI = cbind(PI,RATE_HOUR)
PI = PI[,c(-5, -8:-11)]
library(ggmap)
library(RColorBrewer)
# 서울 기준 open street map 호출
SeoulMap = qmap("seoul", zoom = 11, scale = 4, maptype = "toner-lite", source= 'stamen', legend = "topleft", extent = 'device', size = c(1280,1280))
map1 = SeoulMap +
geom_point(aes(x = LNG, y = LAT, color = RATE_HOUR, size = CAPACITY), data = PI) +
theme(legend.title=element_text(size=8), legend.text = element_text(size = 7)) +
scale_colour_gradientn(colours = brewer.pal(6, "OrRd")) +
labs(size = '수용대수', colour = '주차료') +
coord_equal()
map1
map2 = SeoulMap +
geom_point(aes(x = LNG, y = LAT, color = RATE_HOUR, size = CAPACITY), data = PI) +
geom_density2d(aes(x = LNG, y = LAT, alpha = 0), bins = 6, data = PI) +
stat_density2d(aes(x = LNG, y = LAT, fill = ..level.., alpha = ..level..), data = PI, geom = 'polygon') +
theme(legend.title=element_text(size=8), legend.text = element_text(size = 7)) +
scale_colour_gradientn(colours = brewer.pal(6, "OrRd")) +
labs(size = '수용대수', colour = '주차료', fill = '분포') +
scale_alpha(guide = 'none') +
coord_equal()
map2
서울 지역구 별 인구 데이터 취합
library(xlsx)
population = read.xlsx2('d:/github/r/visualization/final_proj/population_raw.xls', 1, encoding = 'utf-8', stringsAsFactors = F)
pop_info = population[,2:5] %>% filter(동=='소계') %>% dplyr::select(-동)
colnames(pop_info) = c('ADDR', 'HOUSEHOLD', 'POPULATION')
for (i in 2:3)pop_info[,i] = as.numeric(pop_info[,i])
write.csv(pop_info, 'd:/github/r/visualization/final_proj/Population_refined.csv')
PI_by_ADDR = PI%>%dplyr::group_by(ADDR)%>%dplyr::select(CAPACITY,RATE_HOUR)%>%summarise(CAPA = sum(CAPACITY), RATE = as.integer(mean(RATE_HOUR[RATE_HOUR!=0])))
info_by_ADDR = left_join(PI_by_ADDR, pop_info, by = 'ADDR')
info_by_ADDR = info_by_ADDR %>% mutate(CAPA_HOUSE = CAPA/HOUSEHOLD, CAPA_POP = CAPA/POPULATION)
info_by_ADDR = cbind(info_by_ADDR, geocode(info_by_ADDR$ADDR))
#info_by_ADDR = read.csv('d:/github/r/visualization/final_proj/info_by_ADDR.csv', stringsAsFactors = F)[,-1]
info_by_ADDR
## ADDR CAPA RATE HOUSEHOLD POPULATION CAPA_HOUSE CAPA_POP
## 1 강남구 4602 2980 233943 568425 0.019671458 0.0080960549
## 2 강동구 248 2700 180434 453060 0.001374464 0.0005473889
## 3 강북구 1459 2200 141999 330004 0.010274720 0.0044211585
## 4 강서구 2671 1755 250708 606823 0.010653828 0.0044016130
## 5 관악구 1082 1560 254395 523859 0.004253228 0.0020654413
## 6 광진구 1317 2578 159375 371728 0.008263529 0.0035429131
## 7 구로구 1982 1285 172050 445770 0.011519907 0.0044462391
## 8 금천구 1836 2307 105628 254711 0.017381755 0.0072081693
## 9 노원구 1222 2668 219460 566411 0.005568213 0.0021574440
## 10 도봉구 734 1900 136852 347941 0.005363458 0.0021095531
## 11 동대문구 2261 2171 159826 368050 0.014146634 0.0061431871
## 12 동작구 529 1720 172966 411217 0.003058405 0.0012864254
## 13 마포구 2057 2513 169407 388574 0.012142355 0.0052937150
## 14 서대문구 268 2280 137413 326412 0.001950325 0.0008210482
## 15 서초구 3185 2421 173417 447936 0.018366135 0.0071103908
## 16 성동구 3093 2007 131474 311831 0.023525564 0.0099188342
## 17 성북구 865 1737 188100 459276 0.004598618 0.0018833991
## 18 송파구 2581 1571 261155 667898 0.009883020 0.0038643625
## 19 양천구 4523 1523 176861 478119 0.025573756 0.0094599880
## 20 영등포구 5107 3382 166252 402967 0.030718427 0.0126734944
## 21 용산구 2013 2560 106603 243406 0.018883146 0.0082701330
## 22 은평구 1177 1254 201971 492835 0.005827569 0.0023882232
## 23 종로구 916 4839 73091 163521 0.012532323 0.0056017270
## 24 중구 2745 3934 59368 132838 0.046237030 0.0206642678
## 25 중랑구 1908 1828 178185 414783 0.010707972 0.0045999957
## lon lat
## 1 127.0473 37.51724
## 2 127.1238 37.53013
## 3 127.0257 37.63961
## 4 126.8495 37.55098
## 5 126.9516 37.47841
## 6 127.0823 37.53848
## 7 126.8874 37.49540
## 8 126.9020 37.45185
## 9 127.0568 37.65419
## 10 127.0471 37.66877
## 11 127.0400 37.57437
## 12 126.9393 37.51240
## 13 126.9084 37.56376
## 14 126.9368 37.57912
## 15 127.0324 37.48371
## 16 127.0371 37.56334
## 17 127.0182 37.58912
## 18 127.1066 37.51454
## 19 126.8664 37.51687
## 20 126.8962 37.52637
## 21 126.9654 37.53843
## 22 126.9291 37.60270
## 23 126.9794 37.57295
## 24 126.9979 37.56409
## 25 127.0927 37.60656
※지역구 별 주차가능대수와, 주차비 사이에는 양의 상관관계 존재
cor(info_by_ADDR$CAPA, info_by_ADDR$RATE)
## [1] 0.1200081
color_variable = rep(2,length(info_by_ADDR$ADDR))
color_variable[info_by_ADDR$ADDR %in% (info_by_ADDR[,c(1,2)] %>% arrange(desc(CAPA)) %>% .[1:5,1])] = 1
color_variable[info_by_ADDR$ADDR %in% (info_by_ADDR[,c(1,2)] %>% arrange(desc(CAPA)) %>% .[21:25,1])] = 3
map3 = SeoulMap +
geom_point(aes(x = lon, y = lat, colour = as.character(color_variable), size = CAPA), data = info_by_ADDR) +
theme(legend.title=element_text(size=8), legend.text = element_text(size = 7)) +
scale_colour_manual(values = c(brewer.pal(3,'Pastel1')), labels = c('상위5','중간','하위5')) +
scale_size(range=c(1,20)) +
geom_text(data=info_by_ADDR, mapping=aes(x=lon, y=lat, label=ADDR), size=3) +
guides(size = FALSE, color = guide_legend('상위/하위 5')) +
coord_equal()
map3
info_by_ADDR[,c(1,2)] %>% arrange(desc(CAPA)) %>% .[c(1:5, 21:25),]
## ADDR CAPA
## 1 영등포구 5107
## 2 강남구 4602
## 3 양천구 4523
## 4 서초구 3185
## 5 성동구 3093
## 21 성북구 865
## 22 도봉구 734
## 23 동작구 529
## 24 서대문구 268
## 25 강동구 248
color_variable = rep(2,length(info_by_ADDR$ADDR))
color_variable[info_by_ADDR$ADDR %in% (info_by_ADDR[,c(1,6)] %>% arrange(desc(CAPA_HOUSE)) %>% .[1:5,1])] = 1
color_variable[info_by_ADDR$ADDR %in% (info_by_ADDR[,c(1,6)] %>% arrange(desc(CAPA_HOUSE)) %>% .[21:25,1])] = 3
map4 = SeoulMap +
geom_point(aes(x = lon, y = lat, colour = as.character(color_variable), size = CAPA_HOUSE), data = info_by_ADDR) +
theme(legend.title=element_text(size=8), legend.text = element_text(size = 7)) +
scale_colour_manual(values = c(brewer.pal(3,'Pastel1')), labels = c('상위5','중간','하위5')) +
scale_size(range=c(1,20)) +
geom_text(data=info_by_ADDR, mapping=aes(x=lon, y=lat, label=ADDR), size=3) +
guides(size = FALSE, color = guide_legend('상위/하위 5')) +
coord_equal()
map4
info_by_ADDR[,c(1,6)] %>% arrange(desc(CAPA_HOUSE)) %>% .[c(1:5, 21:25),]
## ADDR CAPA_HOUSE
## 1 중구 0.046237030
## 2 영등포구 0.030718427
## 3 양천구 0.025573756
## 4 성동구 0.023525564
## 5 강남구 0.019671458
## 21 성북구 0.004598618
## 22 관악구 0.004253228
## 23 동작구 0.003058405
## 24 서대문구 0.001950325
## 25 강동구 0.001374464
color_variable = rep(2,length(info_by_ADDR$ADDR))
color_variable[info_by_ADDR$ADDR %in% (info_by_ADDR[,c(1,7)] %>% arrange(desc(CAPA_POP)) %>% .[1:5,1])] = 1
color_variable[info_by_ADDR$ADDR %in% (info_by_ADDR[,c(1,7)] %>% arrange(desc(CAPA_POP)) %>% .[21:25,1])] = 3
map5 = SeoulMap +
geom_point(aes(x = lon, y = lat, colour = as.character(color_variable), size = CAPA_POP), data = info_by_ADDR) +
theme(legend.title=element_text(size=8), legend.text = element_text(size = 7)) +
scale_colour_manual(values = c(brewer.pal(3,'Pastel1')), labels = c('상위5','중간','하위5')) +
scale_size(range=c(1,20)) +
geom_text(data=info_by_ADDR, mapping=aes(x=lon, y=lat, label=ADDR), size=3) +
guides(size = FALSE, color = guide_legend('상위/하위 5')) +
coord_equal()
map5
info_by_ADDR[,c(1,7)] %>% arrange(desc(CAPA_POP)) %>% .[c(1:5, 21:25),]
## ADDR CAPA_POP
## 1 중구 0.0206642678
## 2 영등포구 0.0126734944
## 3 성동구 0.0099188342
## 4 양천구 0.0094599880
## 5 용산구 0.0082701330
## 21 관악구 0.0020654413
## 22 성북구 0.0018833991
## 23 동작구 0.0012864254
## 24 서대문구 0.0008210482
## 25 강동구 0.0005473889
서울 지역구 별 주차장 데이터 취합
library(xlsx)
vehicle = read.xlsx2('d:/github/r/visualization/final_proj/vehicle.xls', 1, encoding = 'utf-8', stringsAsFactors = FALSE)
vehicle = vehicle[c(-1,-2),c(2:4)]
colnames(vehicle) = c('ADDR', 'vehicle', 'PARK')
info_by_ADDR = left_join(info_by_ADDR,vehicle, by = 'ADDR')
for (i in 10:11) info_by_ADDR[,i] = as.integer(info_by_ADDR[,i])
info_by_ADDR = mutate(info_by_ADDR, CAPA_car = CAPA/vehicle, Public_PARK = CAPA/PARK)
color_variable = rep(2,length(info_by_ADDR$ADDR))
color_variable[info_by_ADDR$ADDR %in% (info_by_ADDR[,c(1,12)] %>% arrange(desc(CAPA_car)) %>% .[1:5,1])] = 1
color_variable[info_by_ADDR$ADDR %in% (info_by_ADDR[,c(1,12)] %>% arrange(desc(CAPA_car)) %>% .[21:25,1])] = 3
map6 = SeoulMap +
geom_point(aes(x = lon, y = lat, colour = as.character(color_variable), size = CAPA_car), data = info_by_ADDR) +
theme(legend.title=element_text(size=8), legend.text = element_text(size = 7)) +
scale_colour_manual(values = c(brewer.pal(3,'Pastel1')), labels = c('상위5','중간','하위5')) +
scale_size(range=c(1,20)) +
geom_text(data=info_by_ADDR, mapping=aes(x=lon, y=lat, label=ADDR), size=3) +
guides(size = FALSE, color = guide_legend('상위/하위 5')) +
coord_equal()
map6
info_by_ADDR[,c(1,12)] %>% arrange(desc(CAPA_car)) %>% .[c(1:5, 21:25),]
## ADDR CAPA_car
## 1 중구 0.071370999
## 2 영등포구 0.044804534
## 3 성동구 0.039981386
## 4 양천구 0.037025516
## 5 용산구 0.031387897
## 21 노원구 0.009525591
## 22 성북구 0.009001041
## 23 동작구 0.005921067
## 24 서대문구 0.003913551
## 25 강동구 0.002275730
library(scatterpie)
info_by_ADDR$radius = info_by_ADDR$Public_PARK/3
info_by_ADDR = mutate(info_by_ADDR, private_park = PARK-CAPA)
write.csv(info_by_ADDR, 'd:/github/r/visualization/final_proj/info_by_ADDR.csv')
map7 = SeoulMap +
geom_scatterpie(aes(x = lon, y = lat, r= radius), data = info_by_ADDR, cols = c('CAPA','private_park'), alpha = .7, color = NA) +
geom_text(data=info_by_ADDR, mapping=aes(x=lon, y=lat, label=ADDR), size=3) +
coord_equal()
map7
info_by_ADDR[,c(1,13)] %>% arrange(desc(Public_PARK)) %>% .[c(1:5, 21:25),]
## ADDR Public_PARK
## 1 중구 0.086369643
## 2 영등포구 0.056594783
## 3 성동구 0.038331887
## 4 양천구 0.038076557
## 5 금천구 0.036493739
## 21 도봉구 0.008589919
## 22 성북구 0.007782626
## 23 동작구 0.005804703
## 24 서대문구 0.003721809
## 25 강동구 0.002009969
SHINY 활용 시각화
상단의 map 자료 Shiny에 적용
library(shiny)
map_list = c('주차장분포', '주차장분포+heatmap','지역구 단위 분포도','가구수 대비 분포도','인구수 대비 분포도','등록 자가용 대비 분포도','전체 주차장 대비 공영주차장 비유')
ui = fluidPage(
headerPanel('서울시내 공영주차장 분포도'),
sidebarPanel(selectInput('maptype', '분포도', map_list )),
mainPanel(
plotOutput('map')
)
)
server = function(input, output)
{
output$map <- renderPlot({
if(input$maptype =='주차장분포') map1
else if (input$maptype =='주차장분포+heatmap') map2
else if (input$maptype =='지역구 단위 분포도') map3
else if (input$maptype =='가구수 대비 분포도') map4
else if (input$maptype =='인구수 대비 분포도') map5
else if (input$maptype =='등록 자가용 대비 분포도') map6
else map7
})
}
shinyApp(ui = ui, server= server)